#include "util.hpp"
/**
 * Sets up GLUT, creates "window" (better put: valid GL context, since the window is never displayed)
 */

// GLSL vars
GLuint glslProgram;
GLuint fragmentShader;
GLuint glslPrograms[20];
GLint yParam, xParam, texWidth, texHeight, commonLength, xoff, yoff;

// FBO identifier
GLuint fb;

// struct actually being used (set from command line)
struct_textureParameters textureParameters;

int forceSoftware;
bool usingFBO; 
bool attached;
PetePBuffer* pbuff;

const char* shaderSrcBeg = 				 
	"#extension GL_ARB_texture_rectangle : enable\n"
	"uniform sampler2DRect textureY;"
	"uniform sampler2DRect textureX;"
	"uniform int texWidth;"
	"uniform int texHeight;"
	"uniform int commonLength;"
	"uniform int xoff;"
	"uniform int yoff;"
	"void main(void) { ";


void initMatPix(bool forceSofwareRenderer)
{
	forceSoftware=forceSofwareRenderer;
	// global force_software macro

	textureParameters.name = "TEXRECT - float_ARB - RGBA - 32";
	textureParameters.texTarget = GL_TEXTURE_RECTANGLE_EXT;
	textureParameters.texInternalFormat = GL_RGBA32F_ARB;
	//textureParameters.texFormat = GL_RGBA;
	textureParameters.texFormat = GL_RED;

	// init glut and glew
	usingFBO=true;
	if (forceSoftware) {
		usingFBO = false;
	}

	initGLUT();
	initGLEW();

	if (!GLEW_EXT_framebuffer_object) {
#ifdef DEBUG__
		printf("FBO not available\n");
#endif
		usingFBO = false;
	}
#ifdef DEBUG__
	printf("usingFBO = %d\n", usingFBO);
#endif
	// init offscreen framebuffer
	if (usingFBO) {
		initFBO();
	} 
	initGLSL();
}
//void compute(int destID, int xTexID, int yTexID, int w, int h, const char* op, int cLength, int xo, int yo, int x, int y) {
void compute(int destID, int xTexID, int yTexID, int w, int h, const char* op, int cLength, int xo, int yo, int x, int y) {
	if (usingFBO) {
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textureParameters.texTarget, destID, 0);
		if (!checkFramebufferStatus()) {
			printf("glFramebufferTexture2DEXT():\t [FAIL]\n");
			exit (ERROR_FBOTEXTURE);
		}
	}
	else
	{
		if(pbuff) {
			PBuffer_End(pbuff);		
			PBuffer_Destroy(pbuff);	
		}
		initPbuffer(w, h);
	}

	// enable GLSL program
	setActiveShader(op);

	glUniform1i(texWidth, w);
	glUniform1i(texHeight, h);
	glUniform1i(commonLength, cLength);
	glUniform1i(xoff, xo);
	glUniform1i(yoff, yo);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(textureParameters.texTarget, yTexID);
	glUniform1i(yParam,0);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(textureParameters.texTarget, xTexID);
	glUniform1i(xParam, 1);

	if (!usingFBO) {
		PBuffer_End(pbuff);
		PBuffer_Begin(pbuff);
	}

	if (usingFBO) {
		glDrawBuffer (GL_COLOR_ATTACHMENT0_EXT);
	}

	drawQuad(w,h);

	checkGLErrors("render()");
	if (!usingFBO) {
		glBindTexture(textureParameters.texTarget, destID);
//		glCopyTexSubImage2D(textureParameters.texTarget, 0, 0, 0, x, y, w, h);
		glCopyTexSubImage2D(textureParameters.texTarget, 0, x, y, 0, 0, w, h);
		checkGLErrors("copytexsubimage");
	}
}
void cleanupMatPix()
{
	glDetachShader(glslProgram, fragmentShader);
	glDeleteShader(fragmentShader);
	glDeleteProgram(glslProgram);
	if (usingFBO) {
		glDeleteFramebuffersEXT(1,&fb);
	}
	else {
		if(pbuff)
		{
			PBuffer_End(pbuff);		
			PBuffer_Destroy(pbuff);	
		}
	}
}

void initGLUT() {
	int argc = 0; char** argv;
	glutInit ( &argc, argv );
	glutCreateWindow("SAXPY TESTS");
	  if(!usingFBO) {
#ifdef DEBUG__
	      printf("initializing CGL\n");
#endif
	      initCGL();
	  }
  }

void initPbuffer(int w, int h)
{
	CGLContextObj cctx = CGLGetCurrentContext();
#ifdef DEBUG__
    printf("pbuffer texsize = %d, %d\n", w,h);
#endif
    pbuff = PBuffer_Create(cctx, w, h, 32, 0);
    pbuff->target =  textureParameters.texTarget;
    PBuffer_Begin(pbuff);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, w, 0.0, h);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glViewport(0, 0, w, h);
}
/**
 * Sets up GLEW to initialise OpenGL extensions
 */
void initGLEW() {
	int err = glewInit();
	// sanity check
	if (GLEW_OK != err) {
		printf((char*)glewGetErrorString(err));
		exit(ERROR_GLEW);
	}
}

void CGLCheckErr(int err, const char* text) {
	if (err) {
		printf("%s\n", text);
		printf("error : %d\n", err);
	}
}
void initCGL() {
	CGLContextObj cctx = CGLGetCurrentContext();
	if (!cctx) {
		printf("couldn't get current context\n");
	}
	int err;
	CGLPixelFormatAttribute attribs[] = { kCGLPFARobust, kCGLPFANoRecovery,
			kCGLPFADoubleBuffer, kCGLPFAColorSize,
			(CGLPixelFormatAttribute) 128, (CGLPixelFormatAttribute)0 };
	CGLPixelFormatObj fmt;
	long screens;
	err = CGLChoosePixelFormat(attribs, &fmt, &screens);
	CGLCheckErr(err, "create pixel fomat");
	if (!fmt) {
		printf("screens = %d\n", screens);
	}
	if (fmt) {
		long accel, colorbits;
		err = CGLDescribePixelFormat(fmt, 0, kCGLPFAAccelerated, &accel);
		err = CGLDescribePixelFormat(fmt, 0, kCGLPFAColorSize, &colorbits);
		CGLCheckErr(err, "read pixel format");
		if (!err) {
#ifdef DEBUG__
			printf("Accellerated = %d\n", accel);
			printf("Color Bits = %d\n", colorbits);
#endif
		}
		CGLContextObj ctx;
		err = CGLCreateContext(fmt, NULL, &ctx);
		CGLCheckErr(err, "create context");
		CGLDestroyPixelFormat(fmt);
		CGLCheckErr(err, "destroy pixel format");
		if (ctx) {
			err = CGLSetCurrentContext(ctx);
			CGLCheckErr(err, "Set Context");
		}
	}
}

void initFBO(void) {
	// create FBO (off-screen framebuffer)
	glGenFramebuffersEXT(1, &fb);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
	checkGLErrors("created FBO\n");
#ifdef DEBUG__
	printf("framebuffer created: %d\n", fb);
#endif
}
void bindTexToFBO(int tex) {
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, textureParameters.texTarget, tex, 0);
}
void setActiveShader(const char* op)
{
	const char* ops[] = {"+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=", "&&", "||", "++", "--", "dot", "slice", "trans", "!", "%", "negate"};
	for(int i = 0; i < 20; i++)
	{
		if(strcmp(op, ops[i]) == 0)
		{
			glUseProgram(glslPrograms[i]);
			yParam = glGetUniformLocation(glslPrograms[i], "textureY");
			xParam = glGetUniformLocation(glslPrograms[i], "textureX");
			texWidth = glGetUniformLocation(glslPrograms[i], "texWidth");
			texHeight = glGetUniformLocation(glslPrograms[i], "texHeight");
			commonLength = glGetUniformLocation(glslPrograms[i], "commonLength");
			xoff = glGetUniformLocation(glslPrograms[i], "xoff");
			yoff = glGetUniformLocation(glslPrograms[i], "yoff");
			checkGLErrors("setActiveShader");
			break;
		}
	}	
}
void initGLSL(void) {
	glslProgram = glCreateProgram();
	fragmentShader = glCreateShader(GL_FRAGMENT_SHADER_ARB);
	const char* ops[] = {"+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=", "&&", "||", "++", "--", "dot", "slice", "trans", "!", "%", "negate"};
	for(int i = 0; i < 20; i++)
	{
		glslPrograms[i] = glCreateProgram();
		setShader(glslPrograms[i], ops[i]);
	}
}
bool isBinOp(const char* op)
{
	const char* ops[] = {"+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">="};
	for(int i = 0; i < 10; i++)
	{
		if(strcmp(op, ops[i]) == 0)
			return true;
	}
	return false;
}
bool isLogicOp(const char* op)
{
	const char* ops[] = {"&&", "||"};
	for(int i = 0; i < 2; i++)
	{
		if(strcmp(op, ops[i]) == 0)
			return true;
	}
	return false;
}
bool isUnaryOp(const char* op)
{
	const char* ops[] = {"++", "--"};
	for(int i = 0; i < 2; i++)
	{
		if(strcmp(op, ops[i]) == 0)
			return true;
	}
	return false;	
}
void setShaderSrc(const char* op, char* source)
{	
	if(strcmp(op, "dot") == 0)
	{
		sprintf(source,"%s" 
					"  vec4 outFrag = vec4(0);"
					"  vec2 pix = gl_TexCoord[0].st;"
					"  for(float i = .5; i < float(commonLength); i=i+1.0) {"
					"  		vec4 x = texture2DRect(textureX, vec2(i, pix.y));"
					"  		vec4 y = texture2DRect(textureY, vec2(pix.x, i));"
					"		outFrag += x*y;"
					"  }"
					"  gl_FragColor = outFrag;}", shaderSrcBeg);		
	}
	else if(strcmp(op, "slice") == 0)
	{
		sprintf(source,"%s" 
				"  vec2 off = vec2(xoff, yoff);"
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st + off);"
				"  gl_FragColor = x;}", shaderSrcBeg);			
	}
	else if(strcmp(op, "trans") == 0)
	{
		sprintf(source,"%s" 
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].ts);"
				"  gl_FragColor = x;}", shaderSrcBeg);			
	}
	else if(isBinOp(op))
	{
		sprintf(source,"%s" 
				"  vec4 y = texture2DRect(textureY, gl_TexCoord[0].st);"
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(x.r %s y.r);}", shaderSrcBeg, op);			
	}
	else if(isLogicOp(op))
	{
		sprintf(source,"%s" 
				"  vec4 y = texture2DRect(textureY, gl_TexCoord[0].st);"
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(bool(x.r) %s bool(y.r));}", shaderSrcBeg, op);			
	}
	else if(isUnaryOp(op))
	{
		sprintf(source,"%s" 
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(%sx.r);}", shaderSrcBeg, op);			
	}
	else if(strcmp(op, "!") == 0)
	{
		sprintf(source,"%s" 
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(!bool(x.r));}", shaderSrcBeg);			
	}
	else if(strcmp(op, "negate") == 0)
	{
		sprintf(source,"%s" 
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(-x.r);}", shaderSrcBeg);			
	}
	else if(strcmp(op, "%") == 0)
	{
		sprintf(source,"%s" 
				"  vec4 y = texture2DRect(textureY, gl_TexCoord[0].st);"
				"  vec4 x = texture2DRect(textureX, gl_TexCoord[0].st);"
				"  gl_FragColor = vec4(mod(x.r, y.r));}", shaderSrcBeg, op);			
	}
}
void setShader(GLint program, const char* op)
{
	char source[1024];
	setShaderSrc(op, source);
	const char* sptr = source;
#ifdef DEBUG__
	printf("making shader for: %s\n", op);
#endif

	checkGLErrors("setShader: before loading source");
	glShaderSource(fragmentShader, 1, &sptr, NULL);
	checkGLErrors("setShader: after loading source");
	glCompileShader(fragmentShader);
	checkGLErrors("setShader: after compiling");
	printShaderInfoLog(fragmentShader);
	checkGLErrors("setShader: before attaching");
	glAttachShader (program, fragmentShader);
	checkGLErrors("setShader: before linking");
	glLinkProgram(program);
	printProgramInfoLog(program);
	checkGLErrors("setShader");
}

/**
 * Checks for OpenGL errors.
 * Extremely useful debugging function: When developing, 
 * make sure to call this after almost every GL call.
 */
void checkGLErrors(const char *label) {
	GLenum errCode;
	const GLubyte *errStr;

	if ((errCode = glGetError()) != GL_NO_ERROR) {
		errStr = gluErrorString(errCode);
		printf("OpenGL ERROR: ");
		printf((char*)errStr);
		printf("(Label: ");
		printf(label);
		printf(")\n.");
	}
}

void drawQuad(int w, int h)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, w, 0.0, h);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, w, h);

	// and render the quad
	checkGLErrors("before render()");
	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0);
	glVertex2f(0.0, 0.0);
	glTexCoord2f(w, 0.0);
	glVertex2f(w, 0.0);
	glTexCoord2f(w, h);
	glVertex2f(w, h);
	glTexCoord2f(0.0, h);
	glVertex2f(0.0, h);
	glEnd();
}

/**
 * Checks framebuffer status.
 * Copied directly out of the spec, modified to deliver a return value.
 */
bool checkFramebufferStatus() {
	GLenum status;
	status = (GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	switch (status) {
	case GL_FRAMEBUFFER_COMPLETE_EXT:
		return true;
	case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
		printf("Framebuffer incomplete, incomplete attachment\n");
		return false;
	case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
		printf("Unsupported framebuffer format\n");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
		printf("Framebuffer incomplete, missing attachment\n");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
		printf("Framebuffer incomplete, attached images must have same dimensions\n");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
		printf("Framebuffer incomplete, attached images must have same format\n");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
		printf("Framebuffer incomplete, missing draw buffer\n");
		return false;
	case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
		printf("Framebuffer incomplete, missing read buffer\n");
		return false;
	}
	return false;
}

/**
 * error checking for GLSL
 */
void printProgramInfoLog(GLuint obj) {
	GLint infologLength = 0;
	GLint charsWritten = 0;
	char *infoLog;
	glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
	if (infologLength > 1) {
		infoLog = (char *)malloc(infologLength);
		glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf(infoLog);
		printf("\n");
		free(infoLog);
	}
}
void printShaderInfoLog(GLuint obj) {
	GLint infologLength = 0;
	GLint charsWritten = 0;
	char *infoLog;
	glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &infologLength);
	if (infologLength > 1) {
		infoLog = (char *)malloc(infologLength);
		glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf(infoLog);
		printf("\n");
		free(infoLog);
	}
}

/**
 * Prints out given vector for debugging purposes.
 */
void printVector(const float *p, const int N) {
	for (int i=0; i<N; i++)
		printf("%f\n", p[i]);
}
